#include "grphoal.h"
#include "fontcache.h"

namespace agg {
	struct glyph_cache
	{
		avl_node_t			avlnode;
		unsigned int		glyph_code;
		unsigned int		data_size;
		unsigned int		data_type;
		double				advance_x;
		double				advance_y;
		unsigned char		data[4];
	};
} // end of namespace agg

namespace grphfntcache
{

#pragma data_seg("fcdata")
unsigned char fontcache_buffer[cachebuf_total_size];
#pragma data_seg()  
#pragma comment(linker,"/SECTION:fcdata,RWS")

using namespace grphoal;

// static object(s)
procsyncobject fc_procsync;

void fontcache::initialize(void)
{
	autoprocsync sync(fc_procsync);

	// see if we've already initialized
	if (fontcache_magic1 == fntcache_magic1
		&& fontcache_magic2 == fntcache_magic2)
	{
		if (fontcache_magic3 != (unsigned int)this)
			exit(1001);
		return;
	}

	fontcache_magic1 = fntcache_magic1;
	fontcache_magic2 = fntcache_magic2;
	fontcache_magic3 = (unsigned int)this;

	// init font desc free list
	fdesc_list = NULL;
	for (int i = max_font_faces - 1; i >= 0; --i)
	{
		free_font_desc *d = (free_font_desc*)(&fonts[i]);
		d->next = fdesc_list;
		fdesc_list = d;
	}

	total_fonts = 0;
	fonts_avltree = NULL;

	// init block list
	fblock_list = NULL;
	for (int i = cachebuf_total_blocks - 1; i >= 0; --i)
	{
		fcache_block* blk = (fcache_block*)(&blocks[i]);
		blk->font_block.next = fblock_list;
		fblock_list = &(blk->font_block);
	}
	avail_blocks = cachebuf_total_blocks;
}

static int fcache_avl_compare(avl_node_t* a, avl_node_t* b)
{
	fcache* da = AVLNODE_ENTRY(fcache, avlnode, a);
	fcache* db = AVLNODE_ENTRY(fcache, avlnode, b);
	if (da->fontsize > db->fontsize)
		return 1;
	else if (da->fontsize < db->fontsize)
		return -1;
	return 0;
}

static int font_desc_avl_compare(avl_node_t* a, avl_node_t* b)
{
	font_descriptor* da = AVLNODE_ENTRY(font_descriptor, avlnode, a);
	font_descriptor* db = AVLNODE_ENTRY(font_descriptor, avlnode, b);
	int ret = memcmp(&(da->fid), &(db->fid), sizeof(grphoal::uint128_t));
	if (ret > 0) return 1;
	else if (ret < 0) return -1;
	else return 0;
}

font_descriptor* fontcache::find_font(const grphoal::uint128_t& fid)
{
	font_descriptor dummy_node;
	dummy_node.fid = fid;

	autoprocsync sync(fc_procsync);
	avl_node_t* node = avl_find(fonts_avltree, &dummy_node.avlnode, font_desc_avl_compare);
	if (NULL == node) return NULL;
	return AVLNODE_ENTRY(font_descriptor, avlnode, node);
}

fcache* fontcache::find_font_cache(font_descriptor* fdesc, unsigned int fontsize)
{
	if (NULL == fdesc || !fontsize)
		return NULL;

	fcache dummy_node;
	dummy_node.fontsize = fontsize;

	autoprocsync sync(fc_procsync);
	avl_node_t* node = avl_find(fdesc->fcache_tree, &dummy_node.avlnode, fcache_avl_compare);
	if (NULL == node) return NULL;
	return AVLNODE_ENTRY(fcache, avlnode, node);	
}

// this function is not locked
font_descriptor* fontcache::get_free_font_desc(void)
{
	font_descriptor* fdesc;
	if (total_fonts < max_font_faces)
		fdesc = &fonts[total_fonts++];
	else if (fdesc_list)
	{
		fdesc = (font_descriptor*)fdesc_list;
		free_font_desc* free_desc = (free_font_desc*)fdesc;
		fdesc_list = free_desc->next;
	}
	else return NULL;

	memset(fdesc, 0, sizeof(font_descriptor));
	return fdesc;
}

// this function is not locked
fcache_block* fontcache::get_free_font_block(void)
{
	if (NULL == fblock_list || !avail_blocks)
		return NULL;

	fcache_block* block = reinterpret_cast<fcache_block*>(fblock_list);
	fblock_list = block->font_block.next;
	--avail_blocks;
	return block;
}

font_descriptor* fontcache::load_font(const grphoal::uint128_t& fid)
{
	autoprocsync sync(fc_procsync);
	font_descriptor* fdesc = find_font(fid);
	if (fdesc) return fdesc;

	fdesc = get_free_font_desc();
	if (NULL == fdesc) return NULL;
	fdesc->fid = fid;
	return fdesc;
}

fcache* fontcache::load_font_cache(font_descriptor* fdesc, unsigned int fontsize)
{
	if (NULL == fdesc || !fontsize)
		return NULL;

	autoprocsync sync(fc_procsync);
	fcache* fc = find_font_cache(fdesc, fontsize);
	if (fc) return fc;

	fc = reinterpret_cast<fcache*>(get_free_font_block());
	if (NULL == fc) return NULL;

	// initialize the fcache
	fc->freesize = (cachebuf_block_size - sizeof(fcache) + 4) & ~3;
	fc->next = NULL;
	fc->curr_block = reinterpret_cast<fcache_block_desc*>(fc);
	fc->glyphs = NULL;
	fc->fontsize = fontsize;
	return fc;
}

static int fcache_glyph_avl_compare(avl_node_t* a, avl_node_t* b)
{
	agg::glyph_cache* da = AVLNODE_ENTRY(agg::glyph_cache, avlnode, a);
	agg::glyph_cache* db = AVLNODE_ENTRY(agg::glyph_cache, avlnode, b);
	if (da->glyph_code > db->glyph_code)
		return 1;
	else if (da->glyph_code < db->glyph_code)
		return -1;
	return 0;
}

agg::glyph_cache* fontcache::find_glyph(fcache* fc, unsigned int gcode)
{
	if (NULL == fc) return NULL;

	// need lock
	autoprocsync sync(fc_procsync);

	avl_node_t* node = avl_find(fc->glyphs,
		MAKE_FIND_OBJECT(gcode, agg::glyph_cache, glyph_code, avlnode),
		fcache_glyph_avl_compare);
	if (NULL == node) return NULL;
	return AVLNODE_ENTRY(agg::glyph_cache, avlnode, node);
}

agg::glyph_cache* fontcache::cache_glyph(fcache* fc,
		unsigned int glyph_code, unsigned int data_size,
		unsigned int data_type, double advance_x, double advance_y)
{
	if (NULL == fc)
		return NULL;

	// calculate the total size
	unsigned int sz = sizeof(agg::glyph_cache) - 4 + data_size;
	sz = (sz + 3) & ~3;

	// need lock
	autoprocsync sync(fc_procsync);

	agg::glyph_cache *cache;
	fcache_block_desc* block = fc->curr_block;

	if (!block || block->freesize < sz)
	{
		// we need to allocate new block
		fcache_block* blk = get_free_font_block();
		if (NULL == blk) return NULL;

		// initialize the font block
		block = &(blk->font_block);
		block->freesize = (cachebuf_block_size - sizeof(fcache_block_desc) + 4) & ~3;
		if (sz > block->freesize)
		{
			// todo: free block
			return NULL;
		}

		// add to fcache
		block->next = reinterpret_cast<fcache_block_desc*>(fc->next);
		fc->next = block;
		fc->curr_block = block;
		fc->freesize = block->freesize;
	}

	// we have enough size
	cache = reinterpret_cast<agg::glyph_cache*>
		(block->buffer + cachebuf_block_size - fc->freesize);
	fc->freesize -= sz;

	// we fill in the cache structure
	cache->glyph_code = glyph_code;
	cache->data_size = data_size;
	cache->data_type = data_type;
	cache->advance_x = advance_x;
	cache->advance_y = advance_y;

	avl_insert(&(fc->glyphs), &cache->avlnode, fcache_glyph_avl_compare);
	return cache;
}

fontcache* fntcache(void)
{
	static fontcache* cache = NULL;
	if (NULL == cache)
	{
		fontcache* tmp = reinterpret_cast<fontcache*>(fontcache_buffer);
		tmp->initialize();
		cache = tmp;
	}
	return cache;
}

};
